import axios from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import type { CreateAxiosOptions } from './axiosTransform';
import { cloneDeep, isFunction } from 'lodash';
import { RequestOptions, Result } from '/#/axios';
import { RequestEnum } from '@/enums/httpEnum';

export class VAxios {
  private axiosInstance: AxiosInstance;
  private readonly options: CreateAxiosOptions;

  constructor(options: CreateAxiosOptions) {
    this.options = options;
    this.axiosInstance = axios.create(options);
    //初始化拦截器
    this.setupIntercepters();
  }

  /**
   *
   * @param config
   * @description 创建axios 实例
   */
  private createAxios(config: CreateAxiosOptions): void {
    this.axiosInstance = axios.create(config);
  }

  private getTransform() {
    const { transform } = this.options;
    return transform;
  }

  getAxios(): AxiosInstance {
    return this.axiosInstance;
  }

  configAxios(config: CreateAxiosOptions) {
    if (!this.axiosInstance) {
      return;
    }
    this.createAxios(config);
  }

  setHeader(headers: any): void {
    if (!this.axiosInstance) {
      return;
    }
    Object.assign(this.axiosInstance.defaults.headers, headers);
  }

  private setupIntercepters() {
    const transform = this.getTransform();
    if (!transform) {
      return;
    }
    const { responseIntercepters, requestIntercepters, responseInterceptersCatch, requestInterceptersCatch } =
      transform;

    this.axiosInstance.interceptors.request.use((config: any) => {
      if (requestIntercepters && isFunction(requestIntercepters)) {
        config = requestIntercepters(config, this.options);
      }
      return config;
    }, undefined);

    requestInterceptersCatch &&
      isFunction(requestInterceptersCatch) &&
      this.axiosInstance.interceptors.request.use(undefined, requestInterceptersCatch);

    this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
      if (responseIntercepters && isFunction(responseIntercepters)) {
        res = responseIntercepters(res);
      }
      return res;
    }, undefined);

    responseInterceptersCatch &&
      isFunction(responseInterceptersCatch) &&
      this.axiosInstance.interceptors.response.use(undefined, (error) => {
        return responseInterceptersCatch(this.axiosInstance, error);
      });
  }

  request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    let conf: CreateAxiosOptions = cloneDeep(config);
    // cancelToken 如果被深拷贝，会导致最外层无法使用cancel方法来取消请求
    if (config.cancelToken) {
      conf.cancelToken = config.cancelToken;
    }

    const transform = this.getTransform();

    const { requestOptions } = this.options;
    const finalRequestOptions: RequestOptions = Object.assign({}, requestOptions, options);
    const { beforeRequestHook, requestCatchHook, transformResponseHook } = transform || {};
    if (beforeRequestHook && isFunction(beforeRequestHook)) {
      conf = beforeRequestHook(conf, finalRequestOptions);
    }
    conf.requestOptions = finalRequestOptions;

    return new Promise((resolve, reject) => {
      this.axiosInstance
        .request(conf)
        .then((res: AxiosResponse<Result>) => {
          if (transformResponseHook && isFunction(transformResponseHook)) {
            try {
              const result = transformResponseHook(res, finalRequestOptions);
              resolve(result);
            } catch (err) {
              reject(err || new Error('request error!'));
            }
            return;
          }
          resolve(res as unknown as Promise<T>);
        })
        .catch((e: Error | AxiosError) => {
          if (requestCatchHook && isFunction(requestCatchHook)) {
            reject(requestCatchHook(e, finalRequestOptions));
            return;
          }
          reject(e);
        });
    });
  }

  get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.GET }, options);
  }

  post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.POST }, options);
  }

  delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.DELETE }, options);
  }

  put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.PUT }, options);
  }
}
